package singsong;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Hashtable;

import javax.sound.sampled.*;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class SingSong implements ActionListener, ChangeListener, LineListener {
	
	private static String FILENAME = "three_note.wav";
	private JFrame frame;
	private JButton recordButton;	
	private JPanel topControlPanel;
	private JPanel trackPanel;
	private JSlider timeSlider;
	
	private Song song = new Song();
	private ArrayList<TrackController> trackControllers;
	
	private TrackGenerator trackGenerator = null;
	
	private boolean playing = false;
	
	public SingSong() {
		trackControllers = new ArrayList<TrackController>();
	}
	
	private JButton makeButton(String label, ActionListener listener, String command) {
		JButton b = new JButton(label);
		b.addActionListener(listener);
		b.setActionCommand(command);
		return b;
	}
	
	public void createAndShowGUI() {
		frame = new JFrame("SingSong");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		//// Init top controls.
		topControlPanel = new JPanel();
		topControlPanel.setLayout(new BoxLayout(topControlPanel, BoxLayout.Y_AXIS));
		
		// Init playback controls.
		JPanel playbackPanel = new JPanel();
		playbackPanel.setLayout(new FlowLayout());
		
		playbackPanel.add(makeButton(">", this, "play/pause"));
		playbackPanel.add(makeButton("|>", this, "start"));
		recordButton = makeButton("Rec", this, "toggle_recording");
		playbackPanel.add(recordButton);
		playbackPanel.add(makeButton("Open", this, "open"));
		playbackPanel.add(makeButton("Save", this, "save"));
		topControlPanel.add(playbackPanel);
		
		// Init time slider.
		timeSlider = new JSlider(JSlider.HORIZONTAL, 0, 0, 0);
		timeSlider.setPaintLabels(true);
		timeSlider.setPaintTicks(true);
		topControlPanel.add(timeSlider);
		
		//// Init container for tracks.
		trackPanel = new JPanel();
		trackPanel.setLayout(new BoxLayout(trackPanel, BoxLayout.Y_AXIS));
		
		
		//// Put it all together.
		frame.getContentPane().add(topControlPanel, BorderLayout.NORTH);
		frame.getContentPane().add(trackPanel, BorderLayout.SOUTH);

		frame.setLocation(400, 300);
		frame.pack();

		frame.setVisible(true);
	}

	public void actionPerformed(ActionEvent event) {
		if (event.getActionCommand().equals("play/pause")) {
			playing = !playing;
			if (playing)
				playAudibleClips();
			else
				stopClips();
		} else if (event.getActionCommand().equals("start")) {
		} else if (event.getActionCommand().equals("toggle_recording")) {
			if (trackGenerator == null) {
				startRecording();
			} else {
				stopRecording();
			}
		} else if (event.getActionCommand().equals("save")) {
			startSaveFileDialog();
		} else if (event.getActionCommand().equals("open")) {
			startOpenFileDialog();
		}
	}
	
	public void update(LineEvent event) {
	  Clip clip = (Clip)event.getSource();
	  float time = clip.getMicrosecondPosition() / 1000.0f;
	  System.out.println("TIME: " + time);
	  timeSlider.setValue((int)time);
	  if (event.getType() == LineEvent.Type.STOP)
		  stopClips();
	}
	
	private void updateTimeSliderScale() {
		float maxLengthMs = 0;
		for (TrackController controller : trackControllers) {
			Track track = controller.getTrack();
			if (track != null) {
				long lengthMs = (long)(track.getData().getSampleDuration() * track.getData().getSamples().size() * 1000);
				if (lengthMs > maxLengthMs)
					maxLengthMs = lengthMs;
			}
		}
		Hashtable<Integer, JLabel> dictionary = new Hashtable<Integer, JLabel>();
		float interval = (maxLengthMs / 8);
		for (int i = 0; i < 8; i++) {
			dictionary.put((int)(interval * i), new JLabel(String.format("%.1f", interval * i / 1000)));
		}
		timeSlider.setMaximum((int)maxLengthMs);
		timeSlider.setLabelTable(dictionary);
		timeSlider.setMinorTickSpacing((int)interval);
		timeSlider.setMajorTickSpacing((int)interval * 2);
	}
	
	private void stopClips() {
		for (TrackController controller : trackControllers) {
			Track track = controller.getTrack();
			if (track.getClip() != null) {
				Clip clip = track.getClip();
				clip.stop();
			}
		}
	}

	private void playAudibleClips() {
		for (TrackController controller : trackControllers) {
			Track track = controller.getTrack();
			if (track.isAudible() && (track.getClip() != null)) {
				Clip clip = track.getClip();
				clip.addLineListener(this);
				int timeSec = timeSlider.getValue();
				clip.setMicrosecondPosition(timeSec * 1000);
				clip.start();
			}
		}
	}

	private void startRecording() {
		recordButton.setText("Stop");
		
		trackGenerator = new TrackGenerator();
		trackGenerator.record();
	}
	
	private void stopRecording() {
		Track track = trackGenerator.done();
		trackGenerator = null;
		song.getTracks().add(track);
		addToTrackPanel(track);
		
		recordButton.setText("Rec");
		updateTimeSliderScale();
	}
	
	private void startSaveFileDialog() {
		JFileChooser chooser = new JFileChooser();
		int returnVal = chooser.showSaveDialog(frame);
	    if (returnVal == JFileChooser.APPROVE_OPTION) {
	    	try {
	    		SongFileWriter writer = new SongFileWriter();
				writer.write(song, chooser.getSelectedFile());
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    }
	}

	private void startOpenFileDialog() {
		JFileChooser chooser = new JFileChooser();
		int returnVal = chooser.showOpenDialog(frame);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			try {
				SongFileReader reader = new SongFileReader();
				song = reader.read(chooser.getSelectedFile());
				reloadTrackPanelForSong();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	private void reloadTrackPanelForSong() {
		trackControllers.clear();
		for (Track track : song.getTracks()) {
			addToTrackPanel(track);
		}
	}

	private void addToTrackPanel(Track track) {
		TrackController trackController = new TrackController(track);
		trackController.addDeleteListener(new TrackController.DeleteListener() {
			public void delete(TrackController trackController) {
				trackControllers.remove(trackController);
				song.getTracks().remove(trackController.getTrack());
				trackPanel.remove(trackController.getPanel());
				frame.pack();
			}
		});
		trackControllers.add(trackController);
		trackPanel.add(trackController.getPanel());
		frame.pack();
		//trackPanel.updateUI();
	}
	
	public void stateChanged(ChangeEvent event) {
	}

	public static void main(String[] args) {
		final SingSong app = new SingSong();
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				app.createAndShowGUI();
			}
		});
	}
}
